Skip to content

vite + vue3 实现 PWA

步骤

  • 第一步:首先保证 vue3-vite 项目已经被创建并完成依赖安装
  • 第二步:开始安装 PWA 插件
shell
pnpm add vite-plugin-pwa workbox-build workbox-precaching workbox-window -D
  • 第三步:编写 public/service-worker.js SW核心脚本
js
// 发版必须升级版本号!!!
const CACHE_VERSION = "pwa-v1.0.0";
const CACHE_NAME = CACHE_VERSION;

// 缓存白名单(首页 + 静态资源)
const CACHE_ASSETS = ["/", "/index.html"];

// =======================================
// 安装:缓存静态资源
// =======================================
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches
      .open(CACHE_NAME)
      .then((cache) => cache.addAll(CACHE_ASSETS))
      .then(() => self.skipWaiting())
  );
});

// =======================================
// 激活:清理旧版本缓存
// =======================================
self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches
      .keys()
      .then((keys) => {
        return Promise.all(
          keys
            .filter((key) => key !== CACHE_NAME)
            .map((key) => caches.delete(key))
        );
      })
      .then(() => self.clients.claim())
  );
});

// =======================================
// 拦截请求:缓存策略
// =======================================
self.addEventListener("fetch", (event) => {
  const request = event.request;
  const url = new URL(request.url);

  // 只处理同源请求
  if (url.origin !== self.origin) return;

  // 接口 → 不走缓存,只走网络(保证数据最新)
  if (request.url.includes("/api/")) {
    event.respondWith(fetch(request));
    return;
  }

  // 静态资源 → 缓存优先
  event.respondWith(
    caches.match(request).then((cacheRes) => cacheRes || fetch(request))
  );
});

// =======================================
// 接收页面消息:立即更新
// =======================================
self.addEventListener("message", (event) => {
  if (event.data === "refresh") {
    self.skipWaiting();
  }
});
  • 第四步:创建 public/logo.png 桌面图片

  • 第五步:创建 public/manifest.webmanifest 内容如下

js
{
  "name": "管理系统",
  "short_name": "后台",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#409EFF",
  "icons": [
    {
      "src": "logo.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ]
}
  • 第六步:修改 vite.config.js
js
import { VitePWA } from "vite-plugin-pwa";

plugins: [
    vue(),
    VitePWA({
    // 关键:使用自定义 service-worker
    strategies: "injectManifest",
    srcDir: "public",
    filename: "service-worker.js",

    // 关闭自动生成 manifest(我们自己写)
    manifest: false,

    // 开发环境也能调试 PWA
    devOptions: {
        enabled: true,
        type: "module",
    },
    }),
]
  • 第七步:在 main.js 中初始化,在 app.mount("#app"); 下面追加
js
async function initPWA() {
  if ("serviceWorker" in navigator) {
    const { registerSW } = await import("virtual:pwa-register");

    const updateSW = registerSW({
      onNeedRefresh() {
        // 发版了!提示用户更新
        if (confirm("系统有新版本,是否立即更新?")) {
          updateSW(true);
        }
      },
      onOfflineReady() {
        console.log("✅ 离线功能已启用");
      },
    });
  }
}

// 初始化 PWA
initPWA();
  • 第八步:生产环境 nginx 配置
shell
server {
    listen 80;
    server_name 你的域名.com;

    # 自动跳 HTTPS(正式环境强烈建议开)
    # return 301 https://$host$request_uri;

    root /usr/share/nginx/html;  # 改成你 dist 上传的路径
    index index.html index.htm;

    # ==========================================
    # 1. 解决 Vue 刷新 404
    # ==========================================
    location / {
        try_files $uri $uri/ /index.html;
        expires -1; # 页面不缓存
    }

    # ==========================================
    # 2. PWA 核心:service-worker 绝对不缓存
    # ==========================================
    location /service-worker.js {
        expires -1;
        add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
        add_header Content-Type "application/javascript";
        try_files $uri $uri/ /index.html;
    }

    # ==========================================
    # 3. manifest 不缓存(PWA 桌面图标配置)
    # ==========================================
    location /manifest.webmanifest {
        expires -1;
        add_header Cache-Control "no-store";
        add_header Content-Type "application/manifest+json";
    }

    # ==========================================
    # 4. 静态资源缓存(js/css/img/fonts 等)
    # ==========================================
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 7d;                # 缓存 7 天
        add_header Cache-Control "public, max-age=604800, immutable";
        access_log off;
    }

    # ==========================================
    # 5. 接口反向代理(/api/* 转发到后端)
    # ==========================================
    location /api/ {
        proxy_pass http://你的后端IP:端口/api/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        add_header Cache-Control "no-store"; # 接口绝不缓存
    }

    # ==========================================
    # 6. 禁止访问隐藏文件
    # ==========================================
    location ~ /\. {
        deny all;
    }

    # ==========================================
    # 7. 日志
    # ==========================================
    access_log /var/log/nginx/your-project-access.log;
    error_log /var/log/nginx/your-project-error.log;
}

# -------------------------------------------
# HTTPS 版本(正式环境必须用,PWA 推荐 HTTPS)
# -------------------------------------------
# server {
#     listen 443 ssl http2;
#     server_name 你的域名.com;
#     ssl_certificate /etc/nginx/ssl/你的证书.pem;
#     ssl_certificate_key /etc/nginx/ssl/你的密钥.key;
#     ssl_protocols TLSv1.2 TLSv1.3;
#     ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256;
#     ssl_prefer_server_ciphers on;
#     root /usr/share/nginx/html;
#     index index.html;
#     下面配置和上面 80 端口完全一样 ……
# }

测试是否成功

  • 启动开发服务 -> 打开 Chrome F12 → Application [Service Workers → 看到 registered /activated/running,Cache Storage → 看到缓存文件] →
  • F12 → Network → 勾选 Offline → 刷新页面 → 页面依然能显示 = PWA 成功
  • chrome 浏览器右上方有导出图标 → 点击打开 → 沉浸式APP

说明

shell
# vite.config.ts 配置
开启 service-worker 自定义
开启 manifest 自定义
开启 开发环境能调试 PWA

# public/manifest.webmanifest
桌面图标配置

# public/service-worker.js
核心缓存逻辑

# index.html
引入图标配置

# main.ts
注册 PWA + 自动更新提示(最重要)

关键

shell
1. 每次发版必须升级 CACHE_VERSION,例如 pwa-v1.0.1
2. service-worker.js 不能被服务器缓存,需要配置 nginx 
3. 线上环境必须使用 HTTPS